package de.upb.aquarium.water;

import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;

import de.upb.aquarium.World;
import de.upb.aquarium.water.WaveBuffer.InterpolationMode;

public class WaveGenerator {

	public enum WaveStrength
	{
		SMALL,
		MEDIUM,
		BIG,
	};

	private class WavePoint implements Comparable<WavePoint>
	{
		public int x, y;
		public WaveStrength size; 
		
		public WavePoint(int x, int y, WaveStrength size) {
			super();
			this.x = x;
			this.y = y;
			this.size = size;
		}

		@Override
		public int compareTo(WavePoint o) {
			if (x != o.x)
			{
				return x - o.x;
			}
			else if (y != o.y)
			{
				return y - o.y;
			}
			else if (size != o.size)
			{
				if (size == WaveStrength.SMALL)
				{
					return (o.size != WaveStrength.SMALL) ? -1 : 0;
				}
				else if (size == WaveStrength.MEDIUM)
				{
					return (o.size == WaveStrength.MEDIUM) ? 0 : (o.size == WaveStrength.BIG ? -1 : 1);
				}
				else if (size == WaveStrength.BIG)
				{
					return (o.size != WaveStrength.BIG) ? 1 : 0;
				}
			}
			return 0;
		}
	}
	
	private class WaveStatus
	{
		public WaveBuffer buffer;
		public double time;
		
		public WaveStatus(WaveBuffer buffer, double time) {
			super();
			this.buffer = buffer;
			this.time = time;
		}
	}
	
	private TreeMap<WavePoint, WaveBuffer> bufferMap;
	private List<WaveStatus> waves;
	
	private int		horizontalResolution;
	private int		verticalResolution;
	private double	width;
	private double	depth;
	
	private int		timeSteps;
	private double	rotationTime;
	
	private double	amplitudeSmall;
	private double	waveLengthSmall;
	private double	dampeningSmall;
	
	private double	amplitudeMedium;
	private double	waveLengthMedium;
	private double	dampeningMedium;
	
	private double	amplitudeBig;
	private double	waveLengthBig;
	private double	dampeningBig;
	
	public WaveGenerator(
			int horizontalResolution, int verticalResolution,
			double width, double depth,
			int timeSteps, double rotationTime) {
		super();
		this.horizontalResolution = horizontalResolution;
		this.verticalResolution = verticalResolution;
		this.width				= width;
		this.depth				= depth;
		
		this.timeSteps = timeSteps;
		this.rotationTime = rotationTime;
		
		this.amplitudeSmall		= 0.01;
		this.waveLengthSmall	= 0.25;
		this.dampeningSmall		= 0.5;
		
		this.amplitudeMedium	= 0.025;
		this.waveLengthMedium	= 0.5;
		this.dampeningMedium	= 1.0;

		this.amplitudeBig		= 0.05;
		this.waveLengthBig		= 1.0;
		this.dampeningBig		= 2.0;
		
		this.bufferMap			= new TreeMap<WavePoint, WaveBuffer>();
		this.waves				= new LinkedList<WaveStatus>();
	}

	public void addPotentialWave(double centerX, double centerY, WaveStrength strength)
	{
		int cX = (int)Math.round((centerX/width)*horizontalResolution);
		int cY = (int)Math.round((centerY/depth)*verticalResolution);
		
		WavePoint point = new WavePoint(cX, cY, strength);
		
		if (bufferMap.containsKey(point))
		{
			return;
		}
		else
		{
			CircleWave wave;
			switch (strength)
			{
				case SMALL:
					wave = new CircleWave(
							centerX, centerY,
							waveLengthSmall, rotationTime,
							amplitudeSmall, dampeningSmall);
					break;
				case MEDIUM:
					wave = new CircleWave(
							centerX, centerY,
							waveLengthMedium, rotationTime,
							amplitudeMedium, dampeningMedium);
					break;
				case BIG:
					wave = new CircleWave(
							centerX, centerY,
							waveLengthBig, rotationTime,
							amplitudeBig, dampeningBig);
					break;
				default:
					throw new RuntimeException("Unknown wave sort.");
			}
			
			bufferMap.put(point, new CircleWaveBuffer(
							horizontalResolution, verticalResolution,
							width, depth, timeSteps, wave));
		}
	}

	public void generateWave(double centerX, double centerY, WaveStrength strength)
	{
		int cX = (int)Math.round((centerX/width)*horizontalResolution);
		int cY = (int)Math.round((centerY/depth)*verticalResolution);
		
		WavePoint point = new WavePoint(cX, cY, strength);
		
		if (!bufferMap.containsKey(point))
		{
			System.out.println("Warning: Using not precomputed wave!");
			addPotentialWave(centerX, centerY, strength);
		}
		
		waves.add(new WaveStatus(bufferMap.get(point), World.getInstance().getGlobalTime()));
	}
	
	public void fillHeights(float[] highMap, int rowLength)
	{	
		for (int i = 0; i < highMap.length; i++)
		{
			highMap[i] = 0.0f;
		}
		
		List<WaveStatus> toRemove = new LinkedList<WaveStatus>();
		
		for (WaveStatus wave : waves)
		{
			double elapsedTime = World.getInstance().getElapsedTime(wave.time);
			
			if (elapsedTime > wave.buffer.getMaximalLifeTime())
			{
				toRemove.add(wave);
				continue;
			}
			
			wave.buffer.setCurrentTime(elapsedTime, InterpolationMode.LINEAR);
			
			for (int i = 0; i < highMap.length; i++)
			{
				highMap[i] += wave.buffer.getValueAt(i/rowLength, i % rowLength);
			}
		}
	}
	
	public int getTimeSteps() {
		return timeSteps;
	}

	public void setTimeSteps(int timeSteps) {
		this.timeSteps = timeSteps;
	}

	public int getHorizontalResolution() {
		return horizontalResolution;
	}

	public int getVerticalResolution() {
		return verticalResolution;
	}

	public double getDampeningSmall() {
		return dampeningSmall;
	}

	public void setDampeningSmall(double dampeningSmall) {
		this.dampeningSmall = dampeningSmall;
	}

	public double getDampeningBig() {
		return dampeningBig;
	}

	public void setDampeningBig(double dampeningBig) {
		this.dampeningBig = dampeningBig;
	}

	public double getAmplitudeBig() {
		return amplitudeBig;
	}

	public double getAmplitudeMedium() {
		return amplitudeMedium;
	}

	public double getAmplitudeSmall() {
		return amplitudeSmall;
	}

	public double getRotationTime() {
		return rotationTime;
	}

	public double getWaveLengthBig() {
		return waveLengthBig;
	}

	public double getWaveLengthMedium() {
		return waveLengthMedium;
	}

	public double getWaveLengthSmall() {
		return waveLengthSmall;
	}

	public void setAmplitudeBig(double amplitudeBig) {
		this.amplitudeBig = amplitudeBig;
	}

	public void setAmplitudeMedium(double amplitudeMedium) {
		this.amplitudeMedium = amplitudeMedium;
	}

	public void setAmplitudeSmall(double amplitudeSmall) {
		this.amplitudeSmall = amplitudeSmall;
	}

	public void setRotationTime(double rotationTime) {
		this.rotationTime = rotationTime;
	}

	public void setWaveLengthBig(double waveLengthBig) {
		this.waveLengthBig = waveLengthBig;
	}

	public void setWaveLengthMedium(double waveLengthMedium) {
		this.waveLengthMedium = waveLengthMedium;
	}

	public void setWaveLengthSmall(double waveLengthSmall) {
		this.waveLengthSmall = waveLengthSmall;
	}

	public void setDampeningMedium(double dampeningMedium) {
		this.dampeningMedium = dampeningMedium;
	}

	public double getDampeningMedium() {
		return dampeningMedium;
	}
}
